# -*- coding: utf-8 -*-
from __future__ import print_function  # 兼容 Python 2 和 3 的 print 函数
import os
import subprocess
import sys
import re
import sqlite3
from collections import defaultdict

# 全局字典存储数据库连接
db_connections = {}

# 定义四个有效的路径
valid_paths = ['/usr/lib64/', '/usr/lib/', '/lib/', '/lib64/']

def get_modified_paths(elf_file):
    '''去掉文件路径的目录部分，拼接常规路径。 '''
    filename = os.path.basename(elf_file)
    modified_paths = [os.path.join(path, filename) for path in valid_paths]
    return modified_paths

def extract_valid_path(elf_file):
    """遍历四种路径 """
    for valid_path in valid_paths:
        if valid_path in elf_file:
            return elf_file[elf_file.index(valid_path):]
    return None

def create_db_connection(db_path):
    """创建一个新的数据库连接，并将其存储到字典中。"""
    try:
        conn = sqlite3.connect(db_path)
        db_connections[db_path] = conn
        return conn
    except sqlite3.Error as e:
        print("数据库连接失败:", e)
        return None
    
def get_db_connection(db_path):
    """获取数据库连接，如果连接已存在则返回现有连接。"""
    if db_path not in db_connections:
        db_connections[db_path] = sqlite3.connect(db_path)
    return db_connections[db_path]

def close_db_connections():
    """关闭所有数据库连接。"""
    for conn in db_connections.values():
        conn.close()
    db_connections.clear()
    
def resolve_symlink(path):
    """递归解析符号链接，直到得到实际文件的路径，并保留目录结构。"""
    while os.path.islink(path):
        link_target = os.readlink(path)
        if not os.path.isabs(link_target):
            path = os.path.join(os.path.dirname(path), link_target)
        else:
            path = link_target
    return path

def get_dependencies_ldd(elf_file):
    """使用 ldd 获取二进制文件依赖的 .so 库列表，解析符号链接。"""
    try:
        output = subprocess.check_output(['ldd', elf_file], stderr=subprocess.STDOUT, universal_newlines=True)
        print(output)
        so_libs = []    
        so_libs_other = []  

        for line in output.splitlines():
            match = re.search(r'\s+(.+?)\s+=>\s+(.+?)\s+\(0x', line)
            if match:
                so_lib = match.group(2).strip()
                so_lib = resolve_symlink(so_lib)
                so_libs.append(so_lib)
            else:
                so_libs_other.append(line.strip())
        
        return so_libs, so_libs_other 
    except subprocess.CalledProcessError as e:
        print("错误:", e.output)
        return [], []  

def get_function_names_nm(library_file, symbol_type):
    """使用 nm 获取动态库中的符号信息，过滤符号类型。"""
    try:
        output = subprocess.check_output(['nm', '-D', library_file], stderr=subprocess.STDOUT, universal_newlines=True)
        symbols = []

        for line in output.splitlines():
            parts = line.split()            
            if symbol_type == 'T' and len(parts) > 2 and parts[1] == 'T':
                symbols.append(parts[2])           
            elif symbol_type == 'U' and len(parts) == 2 and parts[0] == 'U':
                symbols.append(parts[1])
        return symbols
    except subprocess.CalledProcessError as e:
        print("错误:", e.output)
        return []

def check_symbol_in_database(cen_libname, symbol, db_path):
    """查询数据库中是否存在特定符号记录。"""
    try:
        conn = get_db_connection(db_path)
        if not conn:
            print("{}数据库已经断开连接".format(db_path))
        cursor = conn.cursor()
        query = "SELECT 1 FROM lib_symbol_diff WHERE cen_libname = ? AND symbol_name = ?"
        cursor.execute(query, (cen_libname, symbol,))
        exists = cursor.fetchone() is not None
        return exists  
    except sqlite3.Error as e:
        print("数据库错误:", e)
        return False

def check_Targetsystem_in_database(cen_libname, db_path):
    """查询目标系统中没有的so库"""
    try:
        conn = get_db_connection(db_path)
        if not conn:
            print("{}数据库已经断开连接".format(db_path))
        cursor = conn.cursor()
        query = "SELECT * FROM cq_libname WHERE cq_libname = ?"
        cursor.execute(query, (cen_libname,))
        exists = cursor.fetchone() is not None
        if not exists:
            print("目标系统没有对应的so库{}".format(cen_libname))
    except sqlite3.Error as e:
        print("数据库错误:", e)
        

def check_cen_libname_exists(cen_libname, db_path):
    """查询数据库中存在这个基础库。"""
    try:
        conn = get_db_connection(db_path)
        if not conn:
            print("{}数据库已经断开连接".format(db_path))
        cursor = conn.cursor()
        query = "SELECT 1 FROM centos_lib_rpmpackage_relevance WHERE libname LIKE ?"
        cursor.execute(query, ('%{}%'.format(cen_libname),))
        exists = cursor.fetchone() is not None
        return exists  
    except sqlite3.Error as e:
        print("数据库错误:", e)
        return False

def check_so_library(elf_file, db_path):
    """检查 SO 库的符号差异并记录在日志文件中。"""
    print("直接执行nm的so库{}".format(elf_file))
    symbols = get_function_names_nm(elf_file, 'T')
    # check_Targetsystem_in_database(library_name, db_path)
    library_names = get_modified_paths(elf_file)
    for library_name in library_names:
        for symbol in symbols:
            if check_symbol_in_database(library_name, symbol, db_path):
                print("{}库下的{}符号有差异".format(library_name, symbol))

def check_executable(elf_file, db_path):
    """检查可执行文件的依赖库和符号差异并记录在日志文件中。"""
    so_libs, so_libs_other = get_dependencies_ldd(elf_file)
    print("ldd命令查询的二进制文件：{}，查出来的so库{}".format(elf_file, so_libs))
    symbols_by_library = defaultdict(list)

    for so_lib in so_libs:
        symbols = get_function_names_nm(so_lib, 'U')
        # print("nm查出来的符号差异{}".format(symbols))
        if symbols:
            # check_Targetsystem_in_database(so_lib,db_path)
            symbols_by_library[so_lib].extend(symbols)
        for library, symbols in symbols_by_library.items():
            # print("要查数据库的时候的so库名{}".format(library))
            for symbol in symbols:
                if check_symbol_in_database(library, symbol, db_path):
                    print("{}库下的{}符号有差异".format(library, symbol))
 
def elf_check_symboldiffdb(elf_file):
    
    db_path_diff = './diff_symbol.db'       # symbol差异库
    db_path_cenlibRpm = './cen7.0.db'       # cen库与RPM包的关系库
    db_path_cqlibRpm = './cq24.db'
    if not get_db_connection(db_path_diff): # 判断如果没有这个连接就创建
        create_db_connection(db_path_diff)
    
    if not get_db_connection(db_path_cenlibRpm):
        create_db_connection(db_path_cenlibRpm)
    if not os.path.isfile(elf_file):
        print("错误: 找不到二进制文件", elf_file)

    match = re.search(r'([^/]+?)(?:[-\.].*)?\.so(\.\d+)*$', elf_file)
    
    print("match:{}".format(match))
    if match:
        library_name = match.group(1)
        print("正则表达式查出来的so库名字{}".format(library_name))
        if check_cen_libname_exists(library_name, db_path_cenlibRpm):
            check_so_library(elf_file, db_path_diff)
        else:
            check_executable(elf_file, db_path_diff)
    else:
        check_executable(elf_file, db_path_diff)

    print("--------------检查结束-----------------")

